package org.yeasy.eval.util;

import cn.hutool.core.text.CharSequenceUtil;
import com.udojava.evalex.Expression;
import org.yeasy.common.util.CommonBeanUtil;
import org.yeasy.eval.bean.ValueSourceNode;
import org.yeasy.eval.constant.EvalConstant;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * The type Eval util.
 *
 * @author yangyishe
 * @date 2022年10月20日 10:27
 */
public class EvalUtil {


    /**
     * Calc 2 factor unwrap string.因子去除包装
     *
     * @param s the s
     * @return the string
     * @author yangyishe
     * @date 2022年11月07日 15:59
     */
    public static String calc2FactorUnwrap(String s) {
        return s.substring(1, s.length() - 1);
    }

    /**
     * Calc 2 factor wrap string.因子增加包装(目前用于重新包装, 其他场景待测试)
     *
     * @param s the s
     * @return the string
     * @author yangyishe
     * @date 2022年11月07日 15:59
     */
    public static String calc2FactorWrap(String s) {
        return "\\{" + s + "}";
    }

    /**
     * Calc entity 2 factor value 2 value map map.根据实体类, 获取一个key为value键, 值为object的map
     *
     * @param <T>    the type parameter
     * @param entity the entity
     * @return the map
     * @author yangyishe
     * @date 2022年11月07日 16:38
     */
    public static <T> Map<String, Object> calcEntity2NodeFullValue2FieldValueMap(T entity) {
        Class<?> clazz = entity.getClass();
        List<ValueSourceNode> lstValueSourceNode = EntityAnnotationUtil.calc2DeepFieldNode(clazz);
        Map<String, ValueSourceNode> mapValue2Detail = lstValueSourceNode.stream()
                .filter(o -> !o.getCanRefer())
                .collect(Collectors.toMap(ValueSourceNode::getValue, o -> o));
        List<ValueSourceNode> lstValueSourceNodeRefer = lstValueSourceNode.stream()
                .filter(ValueSourceNode::getCanRefer).collect(Collectors.toList());

        Map<String, Object> mapFactorValue2Value = new HashMap<>();
        for (ValueSourceNode mNode : lstValueSourceNodeRefer) {
            if (CharSequenceUtil.isNotEmpty(mNode.getParentValue())) {
                ValueSourceNode mDetailNode = mapValue2Detail.get(mNode.getParentValue());
                List<?> lstDetail = CommonBeanUtil.invokeGetter(entity, mDetailNode.getFieldName());
                if (lstDetail == null || lstDetail.isEmpty()) {
                    break;
                }
                Function<Object, Object> calc2Value = o -> CommonBeanUtil.invokeGetter(o, mNode.getFieldName());
                List<?> lstDetailValue = lstDetail.stream().map(calc2Value).collect(Collectors.toList());
                mapFactorValue2Value.put(mNode.getFullValue(), lstDetailValue);
            } else {
                mapFactorValue2Value.put(mNode.getValue(), CommonBeanUtil.invokeGetter(entity, mNode.getFieldName()));
            }
        }

        return mapFactorValue2Value;
    }

    /**
     * Find all factor set.发现所有公式因子
     *
     * @param formula the formula
     * @return the set
     * @author yangyishe
     * @date 2022年10月20日 10:27
     */
    public static Set<String> calcFormula2Factors(String formula) {
        Pattern mPat = Pattern.compile(EvalConstant.FORMULA_FACTOR);
        Matcher matcher = mPat.matcher(formula);
        Set<String> factorSet = new HashSet<>();
        while (matcher.find()) {
            factorSet.add(matcher.group());
        }
        return factorSet;
    }

    /**
     * Calc replace list.计算替换公式为值
     *
     * @param formula             the formula
     * @param mapValue2FieldValue the map value 2 field value
     * @return the list
     * @author yangyishe
     * @date 2022年11月07日 17:09
     */
    public static List<String> calcReplace(String formula, Map<String, Object> mapValue2FieldValue) {
        Set<String> setFactor = calcFormula2Factors(formula);
        boolean isMulti = false;
        List<String> lstFormulaResult = new ArrayList<>();
        String formulaResult = formula;

        for (String factorWrap : setFactor) {
            String factorPure = calc2FactorUnwrap(factorWrap);
            String factorWrapNew = calc2FactorWrap(factorPure);
            Object fieldValue = mapValue2FieldValue.get(factorPure);
            if (fieldValue == null) {
                continue;
            }
            if (List.class.isAssignableFrom(fieldValue.getClass())) {
                isMulti = true;
                for (Object mDetail : (List<?>) fieldValue) {
                    lstFormulaResult.add(formulaResult);
                }
            }
            if (isMulti) {
                for (int i = 0; i < lstFormulaResult.size(); i++) {
                    String formulaResultEach = lstFormulaResult.get(i);
                    assert fieldValue instanceof List<?>;
                    Object mValueEach = ((List<?>) fieldValue).get(i);
                    if (mValueEach == null) {
                        continue;
                    }
                    lstFormulaResult.set(i, formulaResultEach.replaceAll(factorWrapNew, mValueEach.toString()));
                }
            } else {
                formulaResult = formulaResult.replaceAll(factorWrapNew, fieldValue.toString());
            }
        }
        return isMulti ? lstFormulaResult : Collections.singletonList(formulaResult);
    }

    /**
     * Calc replace default string.计算替换默认值
     *
     * @param formula      the formula
     * @param defaultValue the default value
     * @return the string
     * @author yangyishe
     * @date 2022年11月08日 09:14
     */
    public static String calcReplaceDefault(String formula, String defaultValue) {
        return formula.replaceAll(EvalConstant.FORMULA_FACTOR, defaultValue);
    }

    /**
     * Calc replace default batch list.
     *
     * @param formulaList  the formula list
     * @param defaultValue the default value
     * @return the list
     * @author yangyishe
     * @date 2022年11月08日 09:15
     */
    public static List<String> calcReplaceDefaultBatch(List<String> formulaList, String defaultValue) {
        return formulaList.stream()
                .map(s -> calcReplaceDefault(s, defaultValue)).collect(Collectors.toList());
    }

    /**
     * Eval decimal big decimal.
     *
     * @param <T>     the type parameter
     * @param formula the formula
     * @param entity  the entity
     * @return the big decimal
     * @author yangyishe
     * @date 2022年11月07日 15:59
     */
    public static <T> List<BigDecimal> evalDecimal(String formula, T entity) {
        if (CharSequenceUtil.isEmpty(formula)) {
            return Collections.singletonList(BigDecimal.ZERO);
        }
        List<String> formulaReplaceStringList = evalString(formula, entity);
        List<String> formulaFillDefaultList = calcReplaceDefaultBatch(formulaReplaceStringList, "0");

        return formulaFillDefaultList.stream().map(s -> new Expression(s).eval()).collect(Collectors.toList());
    }

    /**
     * Eval surface string string.
     *
     * @param <T>     the type parameter
     * @param formula the formula
     * @param entity  the entity
     * @return the string
     * @author yangyishe
     * @date 2022年11月07日 15:59
     */
    public static <T> List<String> evalString(String formula, T entity) {
        Map<String, Object> mapKey2Value = calcEntity2NodeFullValue2FieldValueMap(entity);
        return calcReplace(formula, mapKey2Value);
    }
}
